package com.m4f.city.utils.serializer;

import java.io.ByteArrayInputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.hamcrest.core.IsAnything;

import com.m4f.city.utils.serializer.annotation.M4FSerializable;
import com.m4f.city.utils.serializer.exception.M4FSerializeException;

public class M4FJsonSerializer extends M4FAbstractSerializer {

	@Override
	public byte[] serialize(Object o) throws M4FSerializeException {
		StringBuffer sb = new StringBuffer();
		
		if(!isSerializable(o)) throw new M4FSerializeException();
		
		sb.append(serializeObject(o));
		
		return sb.toString().getBytes();
	}

	private String serializeObject(Object o) {
		StringBuffer sb = new StringBuffer();
		
		if(o != null) {
			// Open object definition
			sb.append("{");
			
			ArrayList<Field> fields = getSerializableFields(o);
			Iterator<Field> it = fields.iterator();
			while(it.hasNext()) {
				Field field = it.next();
				if(isWrapped(field)) {
					sb.append("\"").append(getFieldAnnotation(field).name()).append("\"");
					sb.append(":");
					sb.append(getFieldValueToString(field, o));
				}
				if(!"".equals(sb.toString()) && !sb.toString().endsWith(",") && it.hasNext()) sb.append(",");
			}
						
			// Open object definition
			sb.append("}");

		}
		
		return sb.toString();
	}
	
	public HashMap<String, Object> getMap(Object o) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		
		if(o != null && isSerializable(o)) {
			List<Field> fields = getSerializableFields(o);
			for(Field field : fields) {
				Object value = null;
				if(!field.isAccessible()) field.setAccessible(true);
				try {
					if(field.getType().isAnnotationPresent(M4FSerializable.class)) {
						value = getMap(field.get(o));
					} else {
						value = field.get(o);
					}
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				map.put(getFieldAnnotation(field).name(), value);
			}
		}
		
		return map;
	}
	
	public HashMap<String, Object> getMap(Collection collection, String collectionName) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		List<HashMap<String, Object>> list = new ArrayList<HashMap<String,Object>>();
		
		for(Object o : collection) {
			HashMap<String, Object> objectMap = this.getMap(o);
			list.add(objectMap);
		}
		
		map.put(collectionName, list);		
		return map;
	}
	
	public HashMap<String, Object> getMap_old() {
		HashMap<String, Object> map = new HashMap<String, Object>();
		
		int[] arrayInts = new int[] {1,2,3,4,5};
		String[] arrayStrings = new String[] {"uno", "dos", "tres", "cuatro"};
		ArrayList<String> listString = new ArrayList<String>();
		listString.add("uno");
		listString.add("dos");
		listString.add("tres");
		ArrayList<Integer> listInteger = new ArrayList<Integer>();
		listInteger.add(new Integer(1));
		listInteger.add(new Integer(2));
		listInteger.add(new Integer(3));
		HashMap<String, String> mapa = new HashMap<String, String>();
		mapa.put("uno", "1");
		mapa.put("dos", "2");
		mapa.put("tres", "3");
		mapa.put("cuatro", "4");
		
		map.put("listString", listString);
		map.put("listInteger", listInteger);
		map.put("mapa", mapa);
		//map.put("arrayInts", arrayInts);
		//map.put("arrayStrings", arrayStrings);
		return map;
	}
	
	/*private String serializeArray(Field f, Object o) {
		StringBuffer sb = new StringBuffer();
		Object array;
		
		if(!f.isAccessible()) f.setAccessible(true);
		try {
			array = f.get(o);
			for(int i = 0 ; i < Array.getLength(array) ; i++) {
				sb.append("<item>").append(String.valueOf(Array.get(array, i))).append("</item>");
			}
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sb.toString();
	}*/
	
	protected String getFieldValueToString(Field f, Object o) { // TODO improve this method!!!!
		if(f != null) {
			try {
				if(!f.isAccessible()) f.setAccessible(true);
				
				return String.valueOf(f.get(o));
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return "";
	}
	
	public <T> T deserialize(Class<T> clazz, byte[] bytes) { // TODO improve this method!!!
		T t = null;
		try {
			t = clazz.newInstance();
			JsonParser jsonParser = new JsonFactory().createJsonParser(bytes);
			ObjectMapper mapper = new ObjectMapper();
			// TEST
			//t = mapper.readValue(new ByteArrayInputStream(bytes), clazz);
			jsonParser.setCodec(mapper);

			JsonNode rootNode = jsonParser.readValueAsTree();
			deserializeObject(clazz, jsonParser, rootNode);
		} catch(Exception e) {
			e.printStackTrace();
		}
		
		return t;
	}
	
	public <T> T deserializeObject(Class<T> clazz, JsonParser parser, JsonNode elementNode) {
		T t = null;

		try {
			t = clazz.newInstance();
			
			Map<String, Field> fieldsMap = getSerializableFieldsMap(t);
			Iterator<String> nodeNamesIt = fieldsMap.keySet().iterator();
			while(nodeNamesIt.hasNext()) {
				String nodeName = nodeNamesIt.next();
				Field field = fieldsMap.get(nodeName);
				if(!field.isAccessible()) field.setAccessible(true);
				Object value = null;
				JsonNode node = elementNode.get(nodeName);
				if(node.isValueNode() && !node.isNull()) {
					value = getFieldValueFromString(field, node.getValueAsText());
				} else if(node.isContainerNode()) { // Array for example
					
					boolean test = true;
				}
				if(value != null) field.set(t, value);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
				
		return t;
	}
}
